package org.jmc.refmanager.domain.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.annotation.Resource;
import javax.persistence.Query;

import org.jmc.refmanager.domain.dao.OrgUnitDao;
import org.jmc.refmanager.domain.dao.QueryParam;
import org.jmc.refmanager.domain.dao.UserDao;
import org.jmc.refmanager.domain.model.OrgUnit;
import org.jmc.refmanager.domain.model.User;
import org.jmc.refmanager.domain.model.filter.OrgUnitFilter;
import org.jmc.refmanager.domain.service.OrgUnitManager;
import org.jmc.refmanager.security.SecurityHelper;
import org.jmc.refmanager.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("orgUnitManager")
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public class OrgUnitManagerImpl implements OrgUnitManager {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Resource(name="orgUnitDao")
	private OrgUnitDao orgUnitDao;

	@Resource(name="userDao")
	private UserDao userDao;

	/**
	 * {@inheritDoc}
	 */
	public List<OrgUnit> findAll() {
		return orgUnitDao.findAll();
	}

	/**
	 * La propagation NOT_SUPPORTED suspend la transaction en cours et 
	 * permets de ne pas propager les modifications dnas le cadre d'une 
	 * trabnsaction si cet objet est modifie. Seul un appel explicite
	 * e persist provoque une ecriture (non transactionnelle)
	 * {@inheritDoc}
	 */
	@Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
	public OrgUnit findById(Integer id) {
		return orgUnitDao.find(id);
	}

	/**
	 * {@inheritDoc}
	 */
	public OrgUnit findByIdForUpdate(Integer id) {
		return orgUnitDao.find(id);
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly = false, propagation = Propagation.MANDATORY)
	public OrgUnit update(OrgUnit detachedOrgUnit) {
		return orgUnitDao.merge(detachedOrgUnit);
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void persist(OrgUnit transientOrgUnit) {
		orgUnitDao.persist(transientOrgUnit);
	}

	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly = false)
	public void remove(OrgUnit persistentOrgUnit) {
		orgUnitDao.remove(persistentOrgUnit);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Transactional(readOnly = false)
	public void remove(Integer orgUnitId) {
		orgUnitDao.remove(orgUnitId);
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
	public List<OrgUnit> listDirections() {
		return orgUnitDao.listDirections();
	}

	@Override
	public List<OrgUnit> findScopedServices(String requesterUsername) {
		return this.findScopedServices(null, requesterUsername);
	}

	@Override
	public List<OrgUnit> findScopedServices(final QueryParam<OrgUnit> queryParams, String requesterUsername) {
		// Ajouter le scope de l'utilisateur connecté
        String matricule = SecurityHelper.getAuthentication().getName();
        User user = userDao.getByMatricule(matricule);
        OrgUnit scope = user.getValidationScope();

        QueryParam<OrgUnit> params = checkQueryParamsAndSetDefaults(queryParams, scope);
        OrgUnit example = params.getCriteriaExample();

        StringBuffer jpql = new StringBuffer(200);
        jpql.append("SELECT s FROM OrgUnit s");
        jpql.append(" WHERE s.supprime = false");
        jpql.append(" AND (s.parentPath LIKE CONCAT(?1, '%') OR s.id=?2)");

        List<Object> values = new ArrayList<Object>(10);
        values.add(scope.getPath());
        values.add(scope.getId());
        
        int criteriaIndex = 2;
        if (Util.notEmpty(example.getLibCourt())) {
        	jpql.append(" AND s.libCourt like ?").append(++criteriaIndex);
        	values.add(example.getLibCourt() + "%");
        }
        if (Util.notEmpty(example.getLibLong())) {
            jpql.append(" AND s.libLong like ?").append(++criteriaIndex);
        	values.add(example.getLibLong() + "%");
        }
        if (Util.notEmpty(example.getCodeUO())) {
        	jpql.append(" AND s.codeUO = ?").append(++criteriaIndex);
        	values.add(example.getCodeUO());
        }
        if (Util.notEmpty(example.getCodePostal())) {
        	jpql.append(" AND s.codePostal = ?").append(++criteriaIndex);
        	values.add(example.getCodePostal());
        }
        
        Query query = orgUnitDao.createQuery(jpql.toString());
        List<OrgUnit> services = orgUnitDao.findAndPaging(params, query, values.toArray());
        
//        List<OrgUnit> services = orgUnitDao.findAndPaging(params);
//        services.add(scope);
		return services;
	}
	
	@Override
	public Integer countScopedServices(String requesterUsername) {
		return this.countScopedServices(null, requesterUsername);
	}

	@Override
	public Integer countScopedServices(final QueryParam<OrgUnit> queryParams, String requesterUsername) {
		// Ajouter le scope de l'utilisateur connecte
        String matricule = SecurityHelper.getAuthentication().getName();
        User user = userDao.getByMatricule(matricule);
        OrgUnit scope = user.getValidationScope();
        
		QueryParam<OrgUnit> params = checkQueryParamsAndSetDefaults(queryParams, scope);
		
        OrgUnit example = params.getCriteriaExample();

        StringBuffer jpql = new StringBuffer(200);
        jpql.append("SELECT count(s) FROM OrgUnit s");
        jpql.append(" WHERE s.supprime = false");
        jpql.append(" AND (s.parentPath LIKE CONCAT(?1, '%') OR s.id=?2)");

        List<Object> values = new ArrayList<Object>(10);
        values.add(scope.getPath());
        values.add(scope.getId());
        
        int criteriaIndex = 2;
        if (Util.notEmpty(example.getLibCourt())) {
        	jpql.append(" AND s.libCourt like ?").append(++criteriaIndex);
        	values.add(example.getLibCourt() + "%");
        }
        if (Util.notEmpty(example.getLibLong())) {
            jpql.append(" AND s.libLong like ?").append(++criteriaIndex);
        	values.add(example.getLibLong() + "%");
        }
        if (Util.notEmpty(example.getCodeUO())) {
        	jpql.append(" AND s.codeUO = ?").append(++criteriaIndex);
        	values.add(example.getCodeUO());
        }
        if (Util.notEmpty(example.getCodePostal())) {
        	jpql.append(" AND s.codePostal = ?").append(++criteriaIndex);
        	values.add(example.getCodePostal());
        }
        
        Query query = orgUnitDao.createQuery(jpql.toString());

        return orgUnitDao.count(query, values.toArray());
		// return orgUnitDao.count(params);
	}

	/**
	 * @param queryParams
	 * @return
	 */
	private QueryParam<OrgUnit> checkQueryParamsAndSetDefaults(final QueryParam<OrgUnit> queryParams, OrgUnit scope) {
		QueryParam<OrgUnit> params;
		
		if (queryParams == null) {
			params = new QueryParam<OrgUnit>();
		} else {
			params = queryParams;
		}
		
		// Initialize the criteria example
		if (params.getCriteriaExample() == null) {
			OrgUnit example = new OrgUnit();
			params.setCriteriaExample(example);
		}

		// Limit scope to not deleted services
		params.getCriteriaExample().setSupprime(false);
		
		// Ajouter les criteres par defaut exclude properties
		params.setExcludeProperties(OrgUnitFilter.filterSearchForm());
        if (scope != null) {
	        String scopePath = scope.getPath();
	        params.getCriteriaExample().setParentPath(scopePath);
        }
		
		return params;
	}

	@Override
	public OrgUnit getRoot(String matricule) {
        User user = userDao.getByMatricule(matricule);
		return user.getValidationScope();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<OrgUnit> getChildren(Integer id, String matricule) {
		if (id == 0 || id == null) {
			return Collections.singletonList(getRoot(matricule));
		} else {
			return orgUnitDao.findByNamedQuery(OrgUnit.FIND_DIRECT_CHILDREN, id);
		}
	}

	@Override
	public Integer countChildren(Integer id, String matricule) {
		if (id == 0 || id == null) {
			return 0;
		} else {
			Query query = orgUnitDao.createNamedQuery(OrgUnit.COUNT_DIRECT_CHILDREN);
			return orgUnitDao.count(query, id);
		}
	}

}
